package org.korifey.ffalg;

import java.util.Arrays;

public class AffineFunction {	
	
	@Override
	public String toString() {
		return "AffineFunction [offset=" + offset + ", coeff="
				+ Arrays.toString(coeff) + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(coeff);
		result = prime * result + (int) (offset ^ (offset >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		AffineFunction other = (AffineFunction) obj;
		if (!Arrays.equals(coeff, other.coeff))
			return false;
		if (offset != other.offset)
			return false;
		return true;
	}

	private static final long[] EMPTY_ARR = new long[0];
	
	// fun = offset + coeff[0]*x[0] + factor[1]*x[2]+...
	final private long offset;
	final private long[] coeff;
	
	//memoized
	transient private long _coeffGcd = -1;
	 
	protected AffineFunction(final long offset, final long[] coeff) {
		assert coeff != null; 
		this.offset = offset;
		this.coeff = coeff;
	}
	
	public int getVarNumber() {
		return coeff.length;
	}		
	
	public boolean isConstant() {
		return getVarNumber() == 0;
	}
	
	public boolean containsValue(final long val) {		
		if (coeff.length == 0) {
			return val == offset;
		} else {			
			return (val - offset) % coeffGcd() == 0;
		}
	}
	
	private long coeffGcd() {
		if (_coeffGcd == -1) {
			_coeffGcd = NumberTheory.gcd(coeff);
		}
		return _coeffGcd;
	}
	
	static AffineFunction scalar(final long x) {
		return new AffineFunction(x, EMPTY_ARR);
	}
	
	static AffineFunction newInstance(long offset, final long... coeff) {
		return new AffineFunction(offset, coeff);
	}
	
	static AffineFunction linearFunction(final long... coeff) {
		return newInstance(0, coeff);
	}	

	public long getOffset() {
		return offset;
	}

	public long[] getCoeff() {
		return coeff;
	}

	public long eval(long... x) {
		assert x!= null;
		
		long res = offset;
		for (int i=0; i<Math.min(x.length, coeff.length); i++) {
			res += x[i]*coeff[i];
		}
		return res;
	}
}
